/*
 * MIT License
 *
 * Copyright (c) 2023 北京凯特伟业科技有限公司
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.je.bpm.engine.impl.bpmn.parser.factory;

import com.je.bpm.core.model.MessageEventDefinition;
import com.je.bpm.engine.ActivitiIllegalArgumentException;
import com.je.bpm.engine.delegate.DelegateExecution;
import com.je.bpm.engine.impl.delegate.MessagePayloadMappingProvider;
import com.je.bpm.engine.impl.delegate.ThrowMessage;
import com.je.bpm.engine.impl.el.ExpressionManager;
import com.je.bpm.engine.impl.interceptor.CommandContext;
import com.je.bpm.engine.impl.persistence.entity.EventSubscriptionEntity;
import com.je.bpm.engine.impl.persistence.entity.ExecutionEntity;
import com.je.bpm.engine.impl.persistence.entity.MessageEventSubscriptionEntity;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

public class DefaultMessageExecutionContext implements MessageExecutionContext {

    private final ExpressionManager expressionManager;
    private final MessagePayloadMappingProvider messagePayloadMappingProvider;
    private final MessageEventDefinition messageEventDefinition;

    public DefaultMessageExecutionContext(MessageEventDefinition messageEventDefinition,
                                          ExpressionManager expressionManager,
                                          MessagePayloadMappingProvider messagePayloadMappingProvider) {
        this.messageEventDefinition = messageEventDefinition;
        this.expressionManager = expressionManager;
        this.messagePayloadMappingProvider = messagePayloadMappingProvider;
    }

    @Override
    public String getMessageName(DelegateExecution execution) {
        return evaluateExpression(Optional.ofNullable(messageEventDefinition.getMessageRef())
                        .orElseGet(() -> messageEventDefinition.getMessageExpression()),
                execution);
    }

    public Optional<String> getCorrelationKey(DelegateExecution execution) {
        return Optional.ofNullable(messageEventDefinition.getCorrelationKey())
                .map(correlationKey -> {
                    return evaluateExpression(messageEventDefinition.getCorrelationKey(),
                            execution);
                });
    }


    public Optional<Map<String, Object>> getMessagePayload(DelegateExecution execution) {
        return messagePayloadMappingProvider.getMessagePayload(execution);
    }

    @Override
    public ThrowMessage createThrowMessage(DelegateExecution execution) {
        String name = getMessageName(execution);
        Optional<String> correlationKey = getCorrelationKey(execution);
        Optional<String> businessKey = Optional.ofNullable(execution.getProcessInstanceBusinessKey());
        Optional<Map<String, Object>> payload = getMessagePayload(execution);

        return ThrowMessage.builder()
                .name(name)
                .correlationKey(correlationKey)
                .businessKey(businessKey)
                .payload(payload)
                .build();
    }

    @Override
    public MessageEventSubscriptionEntity createMessageEventSubscription(CommandContext commandContext,
                                                                         DelegateExecution execution) {

        String messageName = getMessageName(execution);
        Optional<String> correlationKey = getCorrelationKey(execution);

        correlationKey.ifPresent(key -> assertNoExistingDuplicateEventSubscriptions(messageName,
                key,
                commandContext));

        MessageEventSubscriptionEntity messageEvent = commandContext.getEventSubscriptionEntityManager()
                .insertMessageEvent(messageName,
                        ExecutionEntity.class.cast(execution));
        correlationKey.ifPresent(messageEvent::setConfiguration);

        return messageEvent;
    }

    public ExpressionManager getExpressionManager() {
        return expressionManager;
    }

    public MessagePayloadMappingProvider getMessagePayloadMappingProvider() {
        return messagePayloadMappingProvider;
    }

    protected String evaluateExpression(String expression,
                                        DelegateExecution execution) {
        return Optional.ofNullable(expressionManager.createExpression(expression))
                .map(it -> it.getValue(execution))
                .map(Object::toString)
                .orElseThrow(() -> new ActivitiIllegalArgumentException("Expression '" + expression + "' is null"));
    }

    protected void assertNoExistingDuplicateEventSubscriptions(String messageName,
                                                               String correlationKey,
                                                               CommandContext commandContext) {

        List<EventSubscriptionEntity> existing = commandContext.getEventSubscriptionEntityManager()
                .findEventSubscriptionsByName("message",
                        messageName,
                        null);
        existing.stream()
                .filter(subscription -> Objects.equals(subscription.getConfiguration(),
                        correlationKey))
                .findFirst()
                .ifPresent(subscription -> {
                    throw new ActivitiIllegalArgumentException("Duplicate message subscription '" + subscription.getEventName() +
                            "' with correlation key '" + subscription.getConfiguration() + "'");
                });

    }
}
